﻿/*-------------------------------------------------------------------------
 * CLR版本:     4.0.30319.42000
 * 创建人:      song
 * 创建时间：   2024/4/10 17:56:20
 * 功 能：      unity 额外函数
 *-------------------------------------------------------------------------*/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
using TMPro;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using SCore.Util;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Debug = UnityEngine.Debug;
using Random = UnityEngine.Random;
#if UNITY_EDITOR
using UnityEditor;
#endif


[PublicAPI]
public static class UnityExtension
{
    #region UI

    #region Transform And RectTransform

    public static void ResetAnchoredPosition3D(this RectTransform target)
    {
        target.anchoredPosition3D = Vector3.zero;
    }

    public static void ResetLocalPosition(this RectTransform target)
    {
        target.localPosition = Vector3.zero;
    }

    public static void ResetLocalScaleToOne(this RectTransform target)
    {
        target.localScale = Vector3.one;
    }

    /// <summary>
    /// Set width of RectTransform with sizeDelta.x
    /// </summary>
    public static void SetWidth(this RectTransform transform, float width)
        => transform.sizeDelta = transform.sizeDelta.SetX(width);

    /// <summary>
    /// Set height of RectTransform with sizeDelta.y
    /// </summary>
    public static void SetHeight(this RectTransform transform, float height)
        => transform.sizeDelta = transform.sizeDelta.SetY(height);


    public static void SetPositionX(this RectTransform transform, float x)
        => transform.anchoredPosition = transform.anchoredPosition.SetX(x);

    public static void SetPositionY(this RectTransform transform, float y)
        => transform.anchoredPosition = transform.anchoredPosition.SetY(y);

    public static void OffsetPositionX(this RectTransform transform, float x)
        => transform.anchoredPosition = transform.anchoredPosition.OffsetX(x);

    public static void OffsetPositionY(this RectTransform transform, float y)
        => transform.anchoredPosition = transform.anchoredPosition.OffsetY(y);

    public static void ResetTransform(this Transform transform, bool needScale = true)
    {
        transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
        if (needScale)
        {
            transform.localScale = Vector3.one;
        }
    }

    public static void SetParentAndResetTransform(this Transform transform, Transform parent)
    {
        transform.SetParent(parent);
        ResetTransform(transform);
    }

    public static void SetPositionX(this Transform transform, float newValue)
    {
        Vector3 v = transform.position;
        v.x = newValue;
        transform.position = v;
    }

    public static void SetPositionY(this Transform transform, float newValue)
    {
        Vector3 v = transform.position;
        v.y = newValue;
        transform.position = v;
    }

    public static void SetPositionZ(this Transform transform, float newValue)
    {
        Vector3 v = transform.position;
        v.z = newValue;
        transform.position = v;
    }

    public static void SetLocalPositionX(this Transform transform, float newValue)
    {
        Vector3 v = transform.localPosition;
        v.x = newValue;
        transform.localPosition = v;
    }

    public static void SetLocalPositionY(this Transform transform, float newValue)
    {
        Vector3 v = transform.localPosition;
        v.y = newValue;
        transform.localPosition = v;
    }

    public static void SetLocalPositionZ(this Transform transform, float newValue)
    {
        Vector3 v = transform.localPosition;
        v.z = newValue;
        transform.localPosition = v;
    }


    /// <summary>
    /// 遍历所有子物体
    /// </summary>
    /// <param name="transform">遍历的根节点</param>
    /// <param name="action">行为</param>
    /// <param name="check">是否执行</param>
    public static void ForEach(this Transform transform, Action<Transform> action,
        Func<Transform, bool> check)
    {
        for (int i = 0; i < transform.childCount; i++)
        {
            Transform childTran = transform.GetChild(i);
            if (check(childTran))
            {
                action(childTran);
            }

            ForEach(childTran, action, check);
        }
    }

    public static void ExecuteAllType1(this Transform transform, Action<Transform> action,
        Func<Transform, bool> check)
    {
        for (int i = 0; i < transform.childCount; i++)
        {
            Transform childTran = transform.GetChild(i);
            if (check(childTran))
            {
                action(childTran);
            }

            ExecuteAllType1(childTran, action, check);
        }
    }

    //递归查找子节点
    public static Transform DeepFindChild(this Transform root, string childName)
    {
        Transform result = null;
        result = root.Find(childName);
        if (result == null)
        {
            foreach (Transform transform in root)
            {
                result = DeepFindChild(transform, childName);
                if (result != null)
                {
                    return result;
                }
            }
        }

        return result;
    }

    //得到所有一级孩子
    public static List<Transform> GetChilds(this Transform root)
    {
        var childs = new List<Transform>(root.childCount + 1);
        for (int index = 0; index < root.childCount; index++)
        {
            childs.Add(root.GetChild(index));
        }

        return childs;
    }

    #endregion

    #region Text

    public static void SetColor(this TextMeshProUGUI target, Color color)
    {
        target.color = new Color(color.r, color.g, color.b);
    }

    public static void SetColor(this TextMeshProUGUI target, string colorString)
    {
        SetColor(target, colorString.ToUnityColor());
    }

    public static void SetColor(this TextMeshPro target, Color color)
    {
        target.color = new Color(color.r, color.g, color.b);
    }

    public static void SetColor(this TextMeshPro target, string colorString)
    {
        SetColor(target, colorString.ToUnityColor());
    }

    public static void SetColor(this Text target, Color color)
    {
        target.color = new Color(color.r, color.g, color.b);
    }

    public static void SetColor(this Text target, string colorString)
    {
        SetColor(target, colorString.ToUnityColor());
    }

    #endregion

    #region Image

    public static void SetColor(this Image target, Color color)
    {
        target.color = new Color(color.r, color.g, color.b);
    }

    public static void SetColor(this Image target, string colorString)
    {
        ColorUtility.TryParseHtmlString("#" + colorString, out Color color);
        SetColor(target, color);
    }

    public static void ToGray(this Image image)
    {
        Shader shader = Shader.Find("FX/Gray");
        if (shader != null)
        {
            Material grayMaterial = new Material(shader);
            image.material = grayMaterial;
            image.SetMaterialDirty();
        }
    }

    public static void ToColored(this Image image)
    {
        image.material = null;
    }

    #endregion

    /// <summary>
    /// Toggle CanvasGroup Alpha, Interactable and BlocksRaycasts settings
    /// </summary>
    public static void SetCanvasState(this CanvasGroup canvas, bool setOn)
    {
        canvas.alpha = setOn ? 1 : 0;
        canvas.interactable = setOn;
        canvas.blocksRaycasts = setOn;
    }

    /// <summary>
    /// Create EventTriggerType Callback entry and subscribe to EventTrigger
    /// </summary>
    public static EventTrigger.Entry OnEventSubscribe(this EventTrigger trigger, EventTriggerType eventType,
        System.Action<BaseEventData> callback)
    {
        EventTrigger.Entry entry = new EventTrigger.Entry();
        entry.eventID = eventType;
        entry.callback = new EventTrigger.TriggerEvent();
        entry.callback.AddListener(new UnityAction<BaseEventData>(callback));
        trigger.triggers.Add(entry);
        return entry;
    }

    #endregion

    #region Layer

    /// <summary>
    /// 设置 layer 包含所有子物体
    /// </summary>
    public static GameObject SetLayerRecursively(this GameObject gameObject, int layer)
    {
        Transform[] transforms = gameObject.GetComponentsInChildren<Transform>(true);
        for (int i = 0; i < transforms.Length; i++)
        {
            transforms[i].gameObject.layer = layer;
        }

        return gameObject;
    }

    public static GameObject SetLayerRecursively(this GameObject source,
        string layerName)
    {
        source.SetLayerRecursively(LayerMask.NameToLayer(layerName));
        return source;
    }

    public static T SetLayerRecursively<T>(this T source, string layerName)
        where T : Component
    {
        source.gameObject.SetLayerRecursively(LayerMask.NameToLayer(layerName));
        return source;
    }

    public static T SetLayerRecursively<T>(this T source, int layer)
        where T : Component
    {
        source.gameObject.SetLayerRecursively(layer);
        return source;
    }

    /// <summary>
    /// 只包含layer的 LayerMask
    /// </summary>
    public static LayerMask ToLayerMask(this int layer)
    {
        return 1 << layer;
    }

    /// <summary>
    /// mask 是否包含layer
    /// </summary>
    /// <param name="mask"></param>
    public static bool LayerInMask(this LayerMask mask, int layer)
    {
        return ((1 << layer) & mask) != 0;
    }

    #endregion

    #region Debug

    private static string[] s_colorTable = new string[]
    {
        "#006457",
        "#b57d1d",
        "#b57d1d",
        "#b57d1d",
    };

    [Conditional("ENABLE_LOG")]
    public static void Log(this string ptr, bool needTime = false)
    {
        UnityPrint(ptr, 0, needTime);
    }

    [Conditional("ENABLE_LOG")]
    public static void Warn(this string ptr, bool needTime = false)
    {
        UnityPrint(ptr, 1, needTime);
    }

    public static void Error(this string ptr, bool needTime = false)
    {
        UnityPrint(ptr, 2, needTime);
    }

    private static void UnityPrint(string ptr, int level, bool needTime = false)
    {
        string time = "";
#if UNITY_EDITOR
        if (needTime)
        {
            time = DateTime.Now.ToString("[HH:mm:ss.fff] ");
        }
#endif
        ptr = $"<color={s_colorTable[level]}>{time}{ptr}</color>";
        UnityEngine.Debug.Log(ptr);
    }

    #region Debug Draw Bounds

    /// <summary>
    /// Draw bounds of Mesh
    /// </summary>
    public static void DrawDebugBounds(MeshFilter mesh, Color color)
    {
#if UNITY_EDITOR
        if (mesh == null) return;
        var renderer = mesh.GetComponent<MeshRenderer>();
        DrawDebugBounds(renderer, color);
#endif
    }

    /// <summary>
    /// Draw bounds of MeshRenderer
    /// </summary>
    public static void DrawDebugBounds(MeshRenderer renderer, Color color)
    {
#if UNITY_EDITOR
        var bounds = renderer.bounds;
        DrawDebugBounds(bounds, color);
#endif
    }

    /// <summary>
    /// Draw bounds of Bounds
    /// </summary>
    public static void DrawDebugBounds(Bounds bounds, Color color)
    {
#if UNITY_EDITOR
        Vector3 v3Center = bounds.center;
        Vector3 v3Extents = bounds.extents;

        var v3FrontTopLeft =
            new Vector3(v3Center.x - v3Extents.x, v3Center.y + v3Extents.y,
                v3Center.z - v3Extents.z); // Front top left corner
        var v3FrontTopRight =
            new Vector3(v3Center.x + v3Extents.x, v3Center.y + v3Extents.y,
                v3Center.z - v3Extents.z); // Front top right corner
        var v3FrontBottomLeft =
            new Vector3(v3Center.x - v3Extents.x, v3Center.y - v3Extents.y,
                v3Center.z - v3Extents.z); // Front bottom left corner
        var v3FrontBottomRight =
            new Vector3(v3Center.x + v3Extents.x, v3Center.y - v3Extents.y,
                v3Center.z - v3Extents.z); // Front bottom right corner
        var v3BackTopLeft =
            new Vector3(v3Center.x - v3Extents.x, v3Center.y + v3Extents.y,
                v3Center.z + v3Extents.z); // Back top left corner
        var v3BackTopRight =
            new Vector3(v3Center.x + v3Extents.x, v3Center.y + v3Extents.y,
                v3Center.z + v3Extents.z); // Back top right corner
        var v3BackBottomLeft =
            new Vector3(v3Center.x - v3Extents.x, v3Center.y - v3Extents.y,
                v3Center.z + v3Extents.z); // Back bottom left corner
        var v3BackBottomRight =
            new Vector3(v3Center.x + v3Extents.x, v3Center.y - v3Extents.y,
                v3Center.z + v3Extents.z); // Back bottom right corner

        Debug.DrawLine(v3FrontTopLeft, v3FrontTopRight, color);
        Debug.DrawLine(v3FrontTopRight, v3FrontBottomRight, color);
        Debug.DrawLine(v3FrontBottomRight, v3FrontBottomLeft, color);
        Debug.DrawLine(v3FrontBottomLeft, v3FrontTopLeft, color);

        Debug.DrawLine(v3BackTopLeft, v3BackTopRight, color);
        Debug.DrawLine(v3BackTopRight, v3BackBottomRight, color);
        Debug.DrawLine(v3BackBottomRight, v3BackBottomLeft, color);
        Debug.DrawLine(v3BackBottomLeft, v3BackTopLeft, color);

        Debug.DrawLine(v3FrontTopLeft, v3BackTopLeft, color);
        Debug.DrawLine(v3FrontTopRight, v3BackTopRight, color);
        Debug.DrawLine(v3FrontBottomRight, v3BackBottomRight, color);
        Debug.DrawLine(v3FrontBottomLeft, v3BackBottomLeft, color);
#endif
    }

    #endregion

    #region Log Array

    private static StringBuilder _stringBuilder;

    private static void PrepareStringBuilder()
    {
        if (_stringBuilder == null) _stringBuilder = new StringBuilder();
        else _stringBuilder.Clear();
    }

    public static void LogArray<T>(T[] toLog)
    {
        PrepareStringBuilder();

        _stringBuilder.Append("Log Array: ").Append(typeof(T).Name).Append(" (").Append(toLog.Length).Append(")\n");
        for (var i = 0; i < toLog.Length; i++)
        {
            _stringBuilder.Append("\n\t").Append(i.ToString().Colored("brown")).Append(": ").Append(toLog[i]);
        }

        _stringBuilder.ToString().Log();
    }

    public static void LogArray<T>(IList<T> toLog)
    {
        PrepareStringBuilder();

        var count = toLog.Count;
        _stringBuilder.Append("Log Array: ").Append(typeof(T).Name).Append(" (").Append(count).Append(")\n");

        for (var i = 0; i < count; i++)
        {
            _stringBuilder.Append("\n\t" + i.ToString().Colored("brown") + ": " + toLog[i]);
        }

        _stringBuilder.ToString().Log();
    }

    #endregion

    #endregion

    #region Collections

    #region IsNullOrEmpty and NotNullOrEmpty

    /// <summary>
    /// Is array null or empty
    /// </summary>
    public static bool IsNullOrEmpty<T>(this T[] collection) => collection == null || collection.Length == 0;

    /// <summary>
    /// Is list null or empty
    /// </summary>
    public static bool IsNullOrEmpty<T>(this IList<T> collection) => collection == null || collection.Count == 0;

    /// <summary>
    /// Is collection null or empty. IEnumerable is relatively slow. Use Array or List implementation if possible
    /// </summary>
    public static bool IsNullOrEmpty<T>(this IEnumerable<T> collection) => collection == null || !collection.Any();

    /// <summary>
    /// Collection is not null or empty
    /// </summary>
    public static bool NotNullOrEmpty<T>(this T[] collection) => !collection.IsNullOrEmpty();

    /// <summary>
    /// Collection is not null or empty
    /// </summary>
    public static bool NotNullOrEmpty<T>(this IList<T> collection) => !collection.IsNullOrEmpty();

    /// <summary>
    /// Collection is not null or empty
    /// </summary>
    public static bool NotNullOrEmpty<T>(this IEnumerable<T> collection) => !collection.IsNullOrEmpty();

    #endregion

    #region InsertAt and RemoveAt

    /// <summary>
    /// Returns new array with inserted empty element at index
    /// </summary>
    public static T[] InsertAt<T>(this T[] array, int index)
    {
        if (index < 0)
        {
            "Index is less than zero. Array is not modified".Error();
            return array;
        }

        if (index > array.Length)
        {
            "Index exceeds array length. Array is not modified".Error();
            return array;
        }

        T[] newArray = new T[array.Length + 1];
        int index1 = 0;
        for (int index2 = 0; index2 < newArray.Length; ++index2)
        {
            if (index2 == index) continue;

            newArray[index2] = array[index1];
            ++index1;
        }

        return newArray;
    }

    /// <summary>
    /// Returns new array without element at index
    /// </summary>
    public static T[] RemoveAt<T>(this T[] array, int index)
    {
        if (index < 0)
        {
            "Index is less than zero. Array is not modified".Error();
            return array;
        }

        if (index >= array.Length)
        {
            "Index exceeds array length. Array is not modified".Error();
            return array;
        }

        T[] newArray = new T[array.Length - 1];
        int index1 = 0;
        for (int index2 = 0; index2 < array.Length; ++index2)
        {
            if (index2 == index) continue;

            newArray[index1] = array[index2];
            ++index1;
        }

        return newArray;
    }

    #endregion

    #region GetRandom

    public static T GetRandom<T>(this T[] collection) => collection[UnityEngine.Random.Range(0, collection.Length)];

    public static T GetRandom<T>(this IList<T> collection) =>
        collection[UnityEngine.Random.Range(0, collection.Count)];

    public static T GetRandom<T>(this IEnumerable<T> collection) =>
        collection.ElementAt(UnityEngine.Random.Range(0, collection.Count()));

    #endregion

    #region IndexOf

    /// <returns>
    /// Returns -1 if none found
    /// </returns>
    public static int IndexOfItem<T>(this IEnumerable<T> collection, T item)
    {
        if (collection == null)
        {
            "IndexOfItem Caused: source collection is null".Error();
            return -1;
        }

        var index = 0;
        foreach (var i in collection)
        {
            if (Equals(i, item)) return index;
            ++index;
        }

        return -1;
    }

    /// <summary>
    /// Is Elements in two collections are the same
    /// </summary>
    public static bool ContentsMatch<T>(this IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first.IsNullOrEmpty() && second.IsNullOrEmpty()) return true;
        if (first.IsNullOrEmpty() || second.IsNullOrEmpty()) return false;

        var firstCount = first.Count();
        var secondCount = second.Count();
        if (firstCount != secondCount) return false;

        foreach (var x1 in first)
        {
            if (!second.Contains(x1)) return false;
        }

        return true;
    }

    /// <summary>
    /// Is Keys in MyDictionary is the same as some collection
    /// </summary>
    public static bool ContentsMatchKeys<T1, T2>(this IDictionary<T1, T2> source, IEnumerable<T1> check)
    {
        if (source.IsNullOrEmpty() && check.IsNullOrEmpty()) return true;
        if (source.IsNullOrEmpty() || check.IsNullOrEmpty()) return false;

        return source.Keys.ContentsMatch(check);
    }

    /// <summary>
    /// Is Values in MyDictionary is the same as some collection
    /// </summary>
    public static bool ContentsMatchValues<T1, T2>(this IDictionary<T1, T2> source, IEnumerable<T2> check)
    {
        if (source.IsNullOrEmpty() && check.IsNullOrEmpty()) return true;
        if (source.IsNullOrEmpty() || check.IsNullOrEmpty()) return false;

        return source.Values.ContentsMatch(check);
    }

    #endregion

    #region GetOrAdd

    /// <summary>
    /// Adds a key/value pair to the IDictionary&lt;TKey,TValue&gt; if the
    /// key does not already exist. Returns the new value, or the existing
    /// value if the key exists.
    /// </summary>
    public static TValue GetOrAddDefault<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key)
        where TValue : new()
    {
        if (!source.ContainsKey(key)) source[key] = new TValue();
        return source[key];
    }

    /// <summary>
    /// Adds a key/value pair to the IDictionary&lt;TKey,TValue&gt; if the
    /// key does not already exist. Returns the new value, or the existing
    /// value if the key exists.
    /// </summary>
    public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> source,
        TKey key, TValue value)
    {
        if (!source.ContainsKey(key)) source[key] = value;
        return source[key];
    }

    /// <summary>
    /// Adds a key/value pair to the IDictionary&lt;TKey,TValue&gt; by using
    /// the specified function if the key does not already exist. Returns
    /// the new value, or the existing value if the key exists.
    /// </summary>
    public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> source,
        TKey key, Func<TValue> valueFactory)
    {
        if (!source.ContainsKey(key)) source[key] = valueFactory();
        return source[key];
    }

    #endregion

    #region ForEach

    /// <summary>
    /// Performs an action on each element of a collection.
    /// </summary>
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        foreach (T element in source) action(element);
        return source;
    }

    /// <summary>
    /// Performs a function on each element of a collection.
    /// </summary>
    public static IEnumerable<T> ForEach<T, R>(this IEnumerable<T> source, Func<T, R> func)
    {
        foreach (T element in source) func(element);
        return source;
    }

    /// <summary>
    /// Performs an action on each element of a collection with its index
    /// passed along.
    /// </summary>
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T, int> action)
    {
        int index = 0;
        foreach (T element in source)
        {
            action(element, index);
            ++index;
        }

        return source;
    }

    /// <summary>
    /// Performs an action on each element of a collection with its index
    /// passed along.
    /// </summary>
    public static IEnumerable<T> ForEach<T, R>(this IEnumerable<T> source, Func<T, int, R> func)
    {
        int index = 0;
        foreach (T element in source)
        {
            func(element, index);
            ++index;
        }

        return source;
    }

    #endregion

    #region Max or Min

    /// <summary>
    /// Find the element of a collection that has the highest selected value.
    /// </summary>
    public static T MaxBy<T, S>(this IEnumerable<T> source, Func<T, S> selector) where S : IComparable<S>
    {
        if (source.IsNullOrEmpty())
        {
            "MaxBy Caused: source collection is null or empty".Error();
            return default(T);
        }

        return source.Aggregate((e, n) => selector(e).CompareTo(selector(n)) > 0 ? e : n);
    }

    /// <summary>
    /// Find the element of a collection that has the lowest selected value.
    /// </summary>
    public static T MinBy<T, S>(this IEnumerable<T> source, Func<T, S> selector) where S : IComparable<S>
    {
        if (source.IsNullOrEmpty())
        {
            "MinBy Caused: source collection is null or empty".Error();
            return default(T);
        }

        return source.Aggregate((e, n) => selector(e).CompareTo(selector(n)) < 0 ? e : n);
    }

    #endregion

    #region FindIndex or List

    /// <summary>
    /// First index of an item that matches a predicate.
    /// </summary>
    public static int FirstIndex<T>(this IList<T> source, Predicate<T> predicate)
    {
        for (int i = 0; i < source.Count; ++i)
        {
            if (predicate(source[i])) return i;
        }

        return -1;
    }

    /// <summary>
    /// First index of an item that matches a predicate.
    /// </summary>
    public static int FirstIndex<T>(this IEnumerable<T> source, Predicate<T> predicate)
    {
        int index = 0;
        foreach (T e in source)
        {
            if (predicate(e)) return index;
            ++index;
        }

        return -1;
    }

    /// <summary>
    /// Last index of an item that matches a predicate.
    /// </summary>
    public static int LastIndex<T>(this IList<T> source, Predicate<T> predicate)
    {
        for (int i = source.Count - 1; i >= 0; --i)
        {
            if (predicate(source[i])) return i;
        }

        return -1;
    }

    /// <summary>
    /// Fills a collection with values generated using a factory function that
    /// passes along their index numbers.
    /// </summary>
    public static IList<T> FillBy<T>(this IList<T> source, Func<int, T> valueFactory)
    {
        for (int i = 0; i < source.Count; ++i) source[i] = valueFactory(i);
        return source;
    }

    /// <summary>
    /// Fills an array with values generated using a factory function that
    /// passes along their index numbers.
    /// </summary>
    public static T[] FillBy<T>(this T[] source, Func<int, T> valueFactory)
    {
        for (int i = 0; i < source.Length; ++i) source[i] = valueFactory(i);
        return source;
    }

    #endregion

    #region Swap

    public static void Swap<T>(this T[] array, int a, int b) => (array[a], array[b]) = (array[b], array[a]);

    #endregion

    #region Copy

    public static List<T> DeepCopy<T>(this List<T> source)
    {
        if (source == null) return null;

        List<T> clonedList = new List<T>();

        foreach (T item in source)
        {
            if (item is ICloneable)
            {
                clonedList.Add((T)(item as ICloneable).Clone());
            }
            else
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(memoryStream, item);
                    memoryStream.Position = 0;
                    clonedList.Add((T)formatter.Deserialize(memoryStream));
                }
            }
        }

        return clonedList;
    }

    public static List<T> DeepCopyData<T>(this List<T> list, Func<T, T> copyFunc) where T : new()
    {
        List<T> listDeep = new List<T>();
        for (var i = 0; i < list.Count; i++)
        {
            listDeep.Add(copyFunc(list[i]));
        }

        return listDeep;
    }

    #endregion

    #endregion

    #region String

    /// <summary>
    /// 返回文件名字
    /// </summary>
    /// <param name="path">文件路径</param>
    /// <param name="includeExtension">是否包含后缀</param>
    /// <returns></returns>
    public static string FileName(this string path, bool includeExtension = true)
    {
        int startIndex = path.LastIndexOfAny(new char[] { '\\', '/' }) + 1;
        int endIndex = includeExtension ? path.Length : path.LastIndexOf('.');
        string fileName = path.Substring(startIndex, endIndex - startIndex);
        return fileName;
    }

    /// <summary>
    /// 文件夹名字包含路径
    /// </summary>
    /// <param name="path">路径</param>
    /// <returns>文件夹名字</returns>
    public static string DirectoryName(this string path)
    {
        return Path.GetDirectoryName(path);
    }

    /// <summary>
    /// 创建文件夹
    /// </summary>
    /// <param name="dirPath">文件夹路径</param>
    public static void CreateDirectory(this string dirPath)
    {
        if (!Directory.Exists(dirPath))
            Directory.CreateDirectory(dirPath);
    }

    /// <summary>
    /// 创建文件夹
    /// </summary>
    /// <param name="filePath">创建文件</param>
    public static void CreateFile(this string filePath)
    {
        if (!File.Exists(filePath))
            File.Create(filePath);
    }

    /// <summary>
    /// texture to string
    /// </summary>
    public static string ImagePathToString(this string path)
    {
        return Convert.ToBase64String(File.ReadAllBytes(path));
    }

    /// <summary>
    /// string to texture
    /// </summary>
    public static Texture ImageFromString(this string source, int width, int height)
    {
        var bytes = Convert.FromBase64String(source);
        var texture = new Texture2D(width, height);
        texture.LoadImage(bytes);
        return texture;
    }

    public static bool IsNullOrEmpty(this string str) => string.IsNullOrEmpty(str);
    public static bool NotNullOrEmpty(this string str) => !string.IsNullOrEmpty(str);

    public static string RemoveStart(this string str, string remove)
    {
        int index = str.IndexOf(remove, StringComparison.Ordinal);
        return index < 0 ? str : str.Remove(index, remove.Length);
    }

    public static string RemoveEnd(this string str, string remove)
    {
        if (!str.EndsWith(remove)) return str;
        return str.Remove(str.LastIndexOf(remove, StringComparison.Ordinal));
    }

    /// <summary>
    /// "Camel case string" => "CamelCaseString" 
    /// </summary>
    public static string ToCamelCase(this string message)
    {
        message = message.Replace("-", " ").Replace("_", " ");
        message = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(message);
        message = message.Replace(" ", "");
        return message;
    }

    /// <summary>
    /// "CamelCaseString" => "Camel Case String"
    /// </summary>
    public static string SplitCamelCase(this string camelCaseString)
    {
        if (string.IsNullOrEmpty(camelCaseString)) return camelCaseString;

        string camelCase = Regex.Replace(Regex.Replace(camelCaseString, @"(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2"),
            @"(\p{Ll})(\P{Ll})", "$1 $2");
        string firstLetter = camelCase.Substring(0, 1).ToUpper();

        if (camelCaseString.Length > 1)
        {
            string rest = camelCase.Substring(1);

            return firstLetter + rest;
        }

        return firstLetter;
    }

    /// <summary>
    /// Convert a string value to an Enum value.
    /// </summary>
    public static T AsEnum<T>(this string source, bool ignoreCase = true) where T : Enum =>
        (T)Enum.Parse(typeof(T), source, ignoreCase);


    /// <summary>
    /// Number presented in Roman numerals
    /// </summary>
    public static string ToRoman(this int i)
    {
        if (i > 999) return "M" + ToRoman(i - 1000);
        if (i > 899) return "CM" + ToRoman(i - 900);
        if (i > 499) return "D" + ToRoman(i - 500);
        if (i > 399) return "CD" + ToRoman(i - 400);
        if (i > 99) return "C" + ToRoman(i - 100);
        if (i > 89) return "XC" + ToRoman(i - 90);
        if (i > 49) return "L" + ToRoman(i - 50);
        if (i > 39) return "XL" + ToRoman(i - 40);
        if (i > 9) return "X" + ToRoman(i - 10);
        if (i > 8) return "IX" + ToRoman(i - 9);
        if (i > 4) return "V" + ToRoman(i - 5);
        if (i > 3) return "IV" + ToRoman(i - 4);
        if (i > 0) return "I" + ToRoman(i - 1);
        return "";
    }

    /// <summary>
    /// Get the "message" string with the "surround" string at the both sides 
    /// </summary>
    public static string SurroundedWith(this string message, string surround) => surround + message + surround;

    /// <summary>
    /// Get the "message" string with the "start" at the beginning and "end" at the end of the string
    /// </summary>
    public static string SurroundedWith(this string message, string start, string end) => start + message + end;

    /// <summary>
    /// Surround string with "color" tag
    /// </summary>
    public static string Colored(this string message, Color color) => $"<color={color.ToHex()}>{message}</color>";

    /// <summary>
    /// Surround string with "color" tag
    /// </summary>
    public static string Colored(this string message, string colorCode) => $"<color={colorCode}>{message}</color>";

    /// <summary>
    /// Surround string with "size" tag
    /// </summary>
    public static string Sized(this string message, int size) => $"<size={size}>{message}</size>";

    /// <summary>
    /// Surround string with "u" tag
    /// </summary>
    public static string Underlined(this string message) => $"<u>{message}</u>";

    /// <summary>
    /// Surround string with "b" tag
    /// </summary>
    public static string Bold(this string message) => $"<b>{message}</b>";

    /// <summary>
    /// Surround string with "i" tag
    /// </summary>
    public static string Italics(this string message) => $"<i>{message}</i>";

    #endregion

    #region Algorithms

    public static T Cast<T>(this IConvertible source) => (T)Convert.ChangeType(source, typeof(T));
    public static bool Is<T>(this object source) => source is T;

    public static T As<T>(this object source) where T : class => source as T;

    public static T Pipe<T>(this T argument, Action<T> action)
    {
        action(argument);
        return argument;
    }

    public static TResult Pipe<T, TResult>(this T argument, Func<T, TResult> function) => function(argument);

    public static T PipeKeep<T, TResult>(this T argument, Func<T, TResult> function)
    {
        function(argument);
        return argument;
    }

    #endregion

    #region Color

    /// <summary>
    /// Returns new Color with Alpha set to a
    /// </summary>
    public static Color WithAlphaSetTo(this Color color, float a) => new Color(color.r, color.g, color.b, a);

    /// <summary>
    /// Set Alpha of Image.Color
    /// </summary>
    public static void SetAlpha(this Graphic graphic, float a) => graphic.color = graphic.color.WithAlphaSetTo(a);

    /// <summary>
    /// Set Alpha of Renderer.Color
    /// </summary>
    public static void SetAlpha(this SpriteRenderer renderer, float a) =>
        renderer.color = renderer.color.WithAlphaSetTo(a);

    /// <summary>
    /// To string of "#b5ff4f" format
    /// </summary>
    public static string ToHex(this Color color) =>
        $"#{(int)(color.r * 255):X2}{(int)(color.g * 255):X2}{(int)(color.b * 255):X2}";

    private const float LightOffset = 0.0625f;

    /// <summary>
    /// Returns a color lighter than the given color.
    /// </summary>
    public static Color Lighter(this Color color) => color.BrightnessOffset(LightOffset);

    /// <summary>
    /// Returns a color darker than the given color.
    /// </summary>
    public static Color Darker(this Color color) => color.BrightnessOffset(-LightOffset);

    /// <summary>
    /// Brightness offset with 1 is brightest and -1 is darkest
    /// </summary>
    public static Color BrightnessOffset(this Color color, float offset)
        => new Color(color.r + offset, color.g + offset, color.b + offset, color.a);

    /// <summary>
    /// Converts a HTML color string into UnityEngine.Color. 
    /// See UnityEngine.ColorUtility.TryParseHtmlString for conversion conditions.
    /// </summary>
    public static Color ToUnityColor(this string source, bool needHash = true)
    {
        var testHash = needHash ? "#" : "";
        ColorUtility.TryParseHtmlString(testHash + source, out Color res);
        return res;
    }

    #endregion

    #region Coroutine

    public static Coroutine SCo(this MonoBehaviour invoker, IEnumerator enumerator)
    {
        return CoRoutineExecutors.Run(enumerator);
    }

    public static Coroutine SCo<T>(this T invoker, IEnumerator enumerator) where T : class
    {
        return CoRoutineExecutors.Run(enumerator);
    }

    public static Coroutine SCo(this IEnumerator invoker)
    {
        return CoRoutineExecutors.Run(invoker);
    }

    /// <summary>
    /// delayed one frame to action
    /// </summary>
    public static void DelayedAction(this MonoBehaviour invoker, Action action)
    {
        invoker.SCo(Coroutine());

        IEnumerator Coroutine()
        {
            yield return null;
            action?.Invoke();
        }
    }

    /// <summary>
    /// delayed waitSeconds to action
    /// </summary>
    public static void DelayedAction(this MonoBehaviour invoker, float waitSeconds, Action action,
        bool unscaled = false)
    {
        invoker.SCo(DelayedActionCoroutine(waitSeconds, action, unscaled));
    }

    private static IEnumerator DelayedActionCoroutine(float waitSeconds, Action action, bool unscaled = false)
    {
        if (unscaled) yield return new WaitForSecondsRealtime(waitSeconds);
        else yield return new WaitForSeconds(waitSeconds);

        if (action != null) action.Invoke();
    }

    #endregion

    #region Math

    /// <summary>
    /// Round float value to nearest integer
    /// </summary>
    public static int RoundToInt(this float val) => Mathf.RoundToInt(val);

    /// <summary>
    /// Swap two reference values
    /// </summary>
    public static void Swap<T>(ref T a, ref T b) => (a, b) = (b, a);

    #endregion

    #region Reflection

    public static bool HasMethod(this object target, string methodName)
    {
        return target.GetType().GetMethod(methodName) != null;
    }

    public static bool HasField(this object target, string fieldName)
    {
        return target.GetType().GetField(fieldName) != null;
    }

    public static bool HasProperty(this object target, string propertyName)
    {
        return target.GetType().GetProperty(propertyName) != null;
    }

    #endregion

    #region Texture

    /// <summary>
    /// Create new sprite out of Texture
    /// </summary>
    public static Sprite AsSprite(this Texture2D texture)
    {
        var rect = new Rect(0, 0, texture.width, texture.height);
        var pivot = new Vector2(0.5f, 0.5f);
        return Sprite.Create(texture, rect, pivot);
    }

    /// <summary>
    /// Change texture size (and scale accordingly)
    /// </summary>
    public static Texture2D Resample(this Texture2D source, int targetWidth, int targetHeight)
    {
        int sourceWidth = source.width;
        int sourceHeight = source.height;
        float sourceAspect = (float)sourceWidth / sourceHeight;
        float targetAspect = (float)targetWidth / targetHeight;

        int xOffset = 0;
        int yOffset = 0;
        float factor;

        if (sourceAspect > targetAspect)
        {
            // crop width
            factor = (float)targetHeight / sourceHeight;
            xOffset = (int)((sourceWidth - sourceHeight * targetAspect) * 0.5f);
        }
        else
        {
            // crop height
            factor = (float)targetWidth / sourceWidth;
            yOffset = (int)((sourceHeight - sourceWidth / targetAspect) * 0.5f);
        }

        var data = source.GetPixels32();
        var data2 = new Color32[targetWidth * targetHeight];
        for (int y = 0; y < targetHeight; y++)
        {
            for (int x = 0; x < targetWidth; x++)
            {
                var p = new Vector2(Mathf.Clamp(xOffset + x / factor, 0, sourceWidth - 1),
                    Mathf.Clamp(yOffset + y / factor, 0, sourceHeight - 1));
                // bilinear filtering
                var c11 = data[Mathf.FloorToInt(p.x) + sourceWidth * (Mathf.FloorToInt(p.y))];
                var c12 = data[Mathf.FloorToInt(p.x) + sourceWidth * (Mathf.CeilToInt(p.y))];
                var c21 = data[Mathf.CeilToInt(p.x) + sourceWidth * (Mathf.FloorToInt(p.y))];
                var c22 = data[Mathf.CeilToInt(p.x) + sourceWidth * (Mathf.CeilToInt(p.y))];

                data2[x + y * targetWidth] = Color.Lerp(Color.Lerp(c11, c12, p.y), Color.Lerp(c21, c22, p.y), p.x);
            }
        }

        var tex = new Texture2D(targetWidth, targetHeight);
        tex.SetPixels32(data2);
        tex.Apply(true);
        return tex;
    }

    /// <summary>
    /// Will texture with solid color
    /// </summary>
    public static Texture2D WithSolidColor(this Texture2D original, Color color)
    {
        var target = new Texture2D(original.width, original.height);
        for (int i = 0; i < target.width; i++)
        {
            for (int j = 0; j < target.height; j++)
            {
                target.SetPixel(i, j, color);
            }
        }

        target.Apply();

        return target;
    }

    /// <summary>
    /// Crop texture to desired size.
    /// Somehow cropped image seemed darker, brightness offset may fix this
    /// </summary>
    public static Texture2D Crop(this Texture2D original, int left, int right, int top, int down,
        float brightnessOffset = 0)
    {
        int x = left + right;
        int y = top + down;
        int resW = original.width - x;
        int resH = original.height - y;
        var pixels = original.GetPixels(left, down, resW, resH);

        if (!Mathf.Approximately(brightnessOffset, 0))
        {
            for (var i = 0; i < pixels.Length; i++)
            {
                pixels[i] = pixels[i].BrightnessOffset(brightnessOffset);
            }
        }

        Texture2D result = new Texture2D(resW, resH, TextureFormat.RGB24, false);
        result.SetPixels(pixels);
        result.Apply();

        return result;
    }

    #endregion

    #region Vector

    #region Set X/Y/Z

    // Set X

    public static Vector3 SetX(this Vector3 vector, float x) => new Vector3(x, vector.y, vector.z);

    public static Vector2 SetX(this Vector2 vector, float x) => new Vector2(x, vector.y);

    public static void SetX(this Transform transform, float x) => transform.position = transform.position.SetX(x);

    // Set Y

    public static Vector3 SetY(this Vector3 vector, float y) => new Vector3(vector.x, y, vector.z);

    public static Vector2 SetY(this Vector2 vector, float y) => new Vector2(vector.x, y);

    public static void SetY(this Transform transform, float y) => transform.position = transform.position.SetY(y);

    // Set Z

    public static Vector3 SetZ(this Vector3 vector, float z) => new Vector3(vector.x, vector.y, z);

    public static Vector3 WithZ(this Vector2 vector, float z) => new Vector3(vector.x, vector.y, z);

    public static void SetZ(this Transform transform, float z) => transform.position = transform.position.SetZ(z);

    // Set XY

    public static Vector3 SetXY(this Vector3 vector, float x, float y) => new Vector3(x, y, vector.z);
    public static Vector3 SetXY(this Vector3 vector, Vector2 xy) => new Vector3(xy.x, xy.y, vector.z);

    public static void SetXY(this Transform transform, float x, float y) =>
        transform.position = transform.position.SetXY(x, y);

    public static void SetXY(this Transform transform, Vector2 xy) =>
        transform.position = transform.position.SetXY(xy);

    // Set XZ

    public static Vector3 SetXZ(this Vector3 vector, float x, float z) => new Vector3(x, vector.y, z);
    public static Vector3 SetXZ(this Vector3 vector, Vector2 xz) => new Vector3(xz.x, vector.y, xz.y);

    public static void SetXZ(this Transform transform, float x, float z) =>
        transform.position = transform.position.SetXZ(x, z);

    public static void SetXZ(this Transform transform, Vector2 xz) =>
        transform.position = transform.position.SetXZ(xz);

    // Set YZ

    public static Vector3 SetYZ(this Vector3 vector, float y, float z) => new Vector3(vector.x, y, z);
    public static Vector3 SetYZ(this Vector3 vector, Vector2 yz) => new Vector3(vector.x, yz.x, yz.y);

    public static void SetYZ(this Transform transform, float y, float z) =>
        transform.position = transform.position.SetYZ(y, z);

    public static void SetYZ(this Transform transform, Vector2 yz) =>
        transform.position = transform.position.SetYZ(yz);

    #endregion

    #region Offset X/Y/Z

    public static Vector3 Offset(this Vector3 vector, Vector2 offset) => vector.OffsetXY(offset);

    // Offset X

    public static Vector3 OffsetX(this Vector3 vector, float x) => new Vector3(vector.x + x, vector.y, vector.z);

    public static Vector2 OffsetX(this Vector2 vector, float x) => new Vector2(vector.x + x, vector.y);

    public static void OffsetX(this Transform transform, float x) =>
        transform.position = transform.position.OffsetX(x);

    // Offset Y

    public static Vector3 OffsetY(this Vector3 vector, float y) => new Vector3(vector.x, vector.y + y, vector.z);

    public static Vector2 OffsetY(this Vector2 vector, float y) => new Vector2(vector.x, vector.y + y);

    public static void OffsetY(this Transform transform, float y) =>
        transform.position = transform.position.OffsetY(y);

    // Offset Z

    public static Vector3 OffsetZ(this Vector3 vector, float z) => new Vector3(vector.x, vector.y, vector.z + z);

    public static void OffsetZ(this Transform transform, float z) =>
        transform.position = transform.position.OffsetZ(z);

    // Offset XY

    public static Vector3 OffsetXY(this Vector3 vector, float x, float y) =>
        new Vector3(vector.x + x, vector.y + y, vector.z);

    public static Vector3 OffsetXY(this Vector3 vector, Vector2 offset) => vector.OffsetXY(offset.x, offset.y);

    public static Vector2 OffsetXY(this Vector2 vector, float x, float y) =>
        new Vector2(vector.x + x, vector.y + y);

    public static void OffsetXY(this Transform transform, float x, float y) =>
        transform.position = transform.position.OffsetXY(x, y);

    public static void OffsetXY(this Transform transform, Vector2 offset) =>
        transform.position = transform.position.OffsetXY(offset);

    // Offset XZ

    public static Vector3 OffsetXZ(this Vector3 vector, float x, float z) =>
        new Vector3(vector.x + x, vector.y, vector.z + z);

    public static Vector3 OffsetXZ(this Vector3 vector, Vector2 offset) => vector.OffsetXZ(offset.x, offset.y);

    public static void OffsetXZ(this Transform transform, float x, float z) =>
        transform.position = transform.position.OffsetXZ(x, z);

    public static void OffsetXZ(this Transform transform, Vector2 offset) =>
        transform.position = transform.position.OffsetXZ(offset);


    public static Vector3 OffsetYZ(this Vector3 vector, float y, float z) =>
        new Vector3(vector.x, vector.y + y, vector.z + z);

    public static Vector3 OffsetYZ(this Vector3 vector, Vector2 offset) => vector.OffsetYZ(offset.x, offset.y);

    public static void OffsetYZ(this Transform transform, float y, float z) =>
        transform.position = transform.position.OffsetYZ(y, z);

    public static void OffsetYZ(this Transform transform, Vector2 offset) =>
        transform.position = transform.position.OffsetYZ(offset);


    //x z to vector2
    public static Vector2 ToVector2(this Vector3 vector3)
    {
        return new Vector2(vector3.x, vector3.z);
    }

    public static Vector3 ToVector3(this Vector2 vector2, float y)
    {
        return new Vector3(vector2.x, y, vector2.y);
    }

    #endregion

    #endregion

    #region Screen shaking

    /// <summary>
    /// Coroutine and Initial Position for transform 
    /// </summary>
    private static Dictionary<Transform, Tuple<Coroutine, Vector3>> _activeShakingTransforms;

    /// <summary>
    /// Shake transform, like for screenshake on camera transform
    /// </summary>
    /// <param name="transform">Transform to shake</param>
    /// <param name="time">Shake time. -1 for infinite shaking</param>
    /// <param name="shakeBounds">Shake amplitude in units</param>
    /// <param name="useUnscaledTime">Shake time should be affected by Time.timeScale or not</param>
    /// <param name="fadeBounds">Fade bounds to zero to the end of the shake</param>
    public static void StartShake(this Transform transform, float time = 0.1f, float shakeBounds = 0.1f,
        bool useUnscaledTime = true, bool fadeBounds = false)
    {
        if (_activeShakingTransforms == null)
            _activeShakingTransforms = new Dictionary<Transform, Tuple<Coroutine, Vector3>>();

        BreakShakeIfAny(transform);

        var coroutine = TransformShakeCoroutine(transform, time, shakeBounds, useUnscaledTime, fadeBounds).SCo();
        _activeShakingTransforms.Add(transform, new Tuple<Coroutine, Vector3>(coroutine, transform.position));
    }

    /// <summary>
    /// Stop Shake for transform, if shaking now
    /// </summary>
    public static void StopShake(this Transform transform)
    {
        BreakShakeIfAny(transform);
    }


    private static IEnumerator TransformShakeCoroutine(Transform transform, float shakeTime, float bounds,
        bool useUnscaledTime, bool fadeBounds)
    {
        Vector3 initialPosition = transform.position;

        float initialBounds = bounds;
        float elapsed = 0;
        while (shakeTime < 0 || elapsed < shakeTime)
        {
            yield return null;

            elapsed += useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
            float elapsedRate = 1 - elapsed / shakeTime;

            float xShake = Random.value * bounds * 2 - bounds;
            float yShake = Random.value * bounds * 2 - bounds;

            Vector3 newPosition = transform.position;
            newPosition.x += xShake;
            newPosition.y += yShake;

            bounds = fadeBounds ? initialBounds * elapsedRate : initialBounds;
            newPosition.x = Mathf.Clamp(newPosition.x, initialPosition.x - bounds, initialPosition.x + bounds);
            newPosition.y = Mathf.Clamp(newPosition.y, initialPosition.y - bounds, initialPosition.y + bounds);

            transform.position = newPosition;
        }

        transform.position = initialPosition;
        _activeShakingTransforms.Remove(transform);
    }

    private static void BreakShakeIfAny(Transform transform)
    {
        if (_activeShakingTransforms == null || !_activeShakingTransforms.ContainsKey(transform)) return;

        var shakeData = _activeShakingTransforms[transform];
        CoRoutineExecutors.Stop(shakeData.Item1);
        transform.position = shakeData.Item2;

        _activeShakingTransforms.Remove(transform);
    }

    #endregion

    #region Animation

    public static void AddEvent(this Animator animator, string stateName, AnimationEvent animationEvent)
    {
        var rider = new AnimatorOverrideController();
        rider.runtimeAnimatorController = animator.runtimeAnimatorController;
        bool isOverride = false;
        if (rider[stateName].events.Length == 0)
        {
            isOverride = true;
            rider[stateName].AddEvent(animationEvent);
        }

        if (isOverride)
        {
            animator.Rebind();
            animator.runtimeAnimatorController = null;
            animator.runtimeAnimatorController = rider;
        }
    }

    /// <summary>
    /// 播放动画 播完后回调
    /// </summary>
    public static Coroutine PlayAnimation(this Animation animation, string animName, Action callback)
    {
        if (animation == null || animName.IsNullOrEmpty())
            return null;
        var animationClip = animation[animName];
        if (animationClip == null)
        {
            $"{animName} clip is null".Error();
            return null;
        }

        animation.Play(animName, PlayMode.StopAll);
        if (callback == null)
        {
            return null;
        }

        var len = animation[animName].clip.length;
        return SCo(DelayedActionCoroutine(len, () => { callback?.Invoke(); }));
    }

    #endregion

    #region Sprite

    /// <summary>
    /// 得到Sprite在图集里的UV
    /// </summary>
    public static Vector4 GetUV(this Sprite sprite)
    {
        return new Vector4(
            sprite.textureRect.x / sprite.texture.width,
            sprite.textureRect.y / sprite.texture.height,
            sprite.textureRect.width / sprite.texture.width,
            sprite.textureRect.height / sprite.texture.height);
    }

    #endregion

    #region Common

    public static T GetOrAddComponent<T>(this Component component) where T : Component
    {
        T target = component.GetComponent<T>();
        if (target == null)
        {
            target = AddMustComponent<T>(component);
        }

        return target;
    }

    public static T GetOrAddComponent<T>(this GameObject gameObject) where T : Component
    {
        T target = gameObject.GetComponent<T>();
        if (target == null)
        {
            target = gameObject.AddComponent<T>();
        }

        return target;
    }

    public static T AddMustComponent<T>(this Component component) where T : Component
    {
        return component.gameObject.AddComponent<T>();
    }

    public static T[] GetChildComponents<T>(this Component component) where T : UnityEngine.Object
    {
        if (component == null || component.transform == null)
        {
            return new T[0];
        }

        List<T> _result = new List<T>();
        int childCount = component.transform.childCount;
        for (int offset = 0; offset < childCount; offset++)
        {
            if (typeof(T) == typeof(GameObject))
            {
                _result.Add(component.transform.GetChild(offset).gameObject as T);
            }
            else if (typeof(T) == typeof(Transform))
            {
                _result.Add(component.transform.GetChild(offset) as T);
            }
            else
            {
                T comp = component.transform.GetChild(offset)?.GetComponent<T>();
                if (comp != null)
                {
                    _result.Add(comp);
                }
            }
        }

        return _result.ToArray();
    }

    public static bool HasComponent<T>(this GameObject gameObject) => gameObject.GetComponent<T>() != null;
    public static bool HasComponent<T>(this Component component) => component.GetComponent<T>() != null;

    public static void SetActive(this Component component, bool active)
    {
        component.gameObject.SetActive(active);
    }

    public static string FormatFileSize(this long bytes)
    {
        string[] sizes = { "Bytes", "KB", "MB", "GB", "TB" };
        int order = 0;
        while (bytes >= 1024 && order < sizes.Length - 1)
        {
            order++;
            bytes = bytes / 1024;
        }

        return String.Format("{0:0.##} {1}", bytes, sizes[order]);
    }

    #endregion


#if UNITY_EDITOR

    #region SerializedProperty

    #region Collections Handling

    /// <summary>
    /// Get array of property childs, if parent property is array
    /// </summary>
    public static SerializedProperty[] AsArray(this SerializedProperty property)
    {
        List<SerializedProperty> items = new List<SerializedProperty>();
        for (int i = 0; i < property.arraySize; i++)
            items.Add(property.GetArrayElementAtIndex(i));
        return items.ToArray();
    }

    /// <summary>
    /// Get array of property childs casted to specific type
    /// </summary>
    public static T[] AsArray<T>(this SerializedProperty property)
    {
        var propertiesArray = property.AsArray();
        return propertiesArray.Select(s => s.objectReferenceValue).OfType<T>().ToArray();
    }

    /// <summary>
    /// Get array of property childs, if parent property is array
    /// </summary>
    public static IEnumerable<SerializedProperty> AsIEnumerable(this SerializedProperty property)
    {
        for (int i = 0; i < property.arraySize; i++)
            yield return property.GetArrayElementAtIndex(i);
    }

    /// <summary>
    /// If property is array, insert new element at the end and get it as a property
    /// </summary>
    public static SerializedProperty NewElement(this SerializedProperty property)
    {
        int newElementIndex = property.arraySize;
        property.InsertArrayElementAtIndex(newElementIndex);
        return property.GetArrayElementAtIndex(newElementIndex);
    }

    #endregion

    /// <summary>
    /// Property is float, int, vector or int vector
    /// </summary>
    public static bool IsNumerical(this SerializedProperty property)
    {
        var propertyType = property.propertyType;
        switch (propertyType)
        {
            case SerializedPropertyType.Float:
            case SerializedPropertyType.Integer:
            case SerializedPropertyType.Vector2:
            case SerializedPropertyType.Vector3:
            case SerializedPropertyType.Vector4:
            case SerializedPropertyType.Vector2Int:
            case SerializedPropertyType.Vector3Int:
                return true;

            default: return false;
        }
    }

    /// <summary>
    /// Get string representation of serialized property
    /// </summary>
    public static string AsStringValue(this SerializedProperty property)
    {
        switch (property.propertyType)
        {
            case SerializedPropertyType.String:
                return property.stringValue;

            case SerializedPropertyType.Character:
            case SerializedPropertyType.Integer:
                if (property.type == "char") return Convert.ToChar(property.intValue).ToString();
                return property.intValue.ToString();

            case SerializedPropertyType.ObjectReference:
                return property.objectReferenceValue != null ? property.objectReferenceValue.ToString() : "null";

            case SerializedPropertyType.Boolean:
                return property.boolValue.ToString();

            case SerializedPropertyType.Enum:
                return property.GetValue().ToString();

            default:
                return string.Empty;
        }
    }

    /// <summary>
    /// Combination of Owner Type and Property Path hashes
    /// </summary>
    public static int GetUniquePropertyId(this SerializedProperty property)
        => property.serializedObject.targetObject.GetType().GetHashCode()
           + property.propertyPath.GetHashCode();

    /// <summary>
    /// Property path for collection without ".Array.data[x]" in it
    /// </summary>
    public static string GetFixedPropertyPath(this SerializedProperty property) =>
        property.propertyPath.Replace(".Array.data[", "[");


    /// <summary>
    /// Get FieldInfo out of SerializedProperty
    /// </summary>
    public static FieldInfo GetFieldInfo(this SerializedProperty property)
    {
        var targetObject = property.serializedObject.targetObject;
        var targetType = targetObject.GetType();

        FieldInfo fieldInfo = null;
        while (targetType != null)
        {
            fieldInfo = targetType.GetField(property.propertyPath);
            if (fieldInfo != null) break;
            targetType = targetType.BaseType;
        }

        return fieldInfo;
    }

    /// <summary>
    /// Get raw object value out of the SerializedProperty
    /// </summary>
    public static object GetValue(this SerializedProperty property)
    {
        if (property == null) return null;

        object obj = property.serializedObject.targetObject;
        var elements = property.GetFixedPropertyPath().Split('.');
        foreach (var element in elements)
        {
            if (element.Contains("["))
            {
                var elementName = element.Substring(0, element.IndexOf("[", StringComparison.Ordinal));
                var index = Convert.ToInt32(element.Substring(element.IndexOf("[", StringComparison.Ordinal))
                    .Replace("[", "").Replace("]", ""));
                obj = GetValueByArrayFieldName(obj, elementName, index);
            }
            else obj = GetValueByFieldName(obj, element);
        }

        return obj;


        object GetValueByArrayFieldName(object source, string name, int index)
        {
            if (!(GetValueByFieldName(source, name) is IEnumerable enumerable)) return null;
            var enumerator = enumerable.GetEnumerator();

            for (var i = 0; i <= index; i++)
                if (!enumerator.MoveNext())
                    return null;
            return enumerator.Current;
        }

        // Search "source" object for a field with "name" and get it's value
        object GetValueByFieldName(object source, string name)
        {
            if (source == null) return null;
            var type = source.GetType();

            while (type != null)
            {
                var fieldInfo = type.GetField(name,
                    BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
                if (fieldInfo != null) return fieldInfo.GetValue(source);

                var propertyInfo = type.GetProperty(name,
                    BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (propertyInfo != null) return propertyInfo.GetValue(source, null);

                type = type.BaseType;
            }

            return null;
        }
    }


    /// <summary>
    /// Set raw object value to the SerializedProperty
    /// </summary>
    public static void SetValue(this SerializedProperty property, object value)
    {
        GetFieldInfo(property).SetValue(property.serializedObject.targetObject, value);
    }

    /// <summary>
    /// Is specific attribute defined on SerializedProperty
    /// </summary>
    /// <param name="property"></param>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public static bool IsAttributeDefined<T>(this SerializedProperty property) where T : Attribute
    {
        var fieldInfo = property.GetFieldInfo();
        if (fieldInfo == null) return false;
        return Attribute.IsDefined(fieldInfo, typeof(T));
    }

    /// <summary>
    /// Repaint inspector window where this property is displayed
    /// </summary>
    public static void Repaint(this SerializedProperty property)
    {
        foreach (var item in ActiveEditorTracker.sharedTracker.activeEditors)
        {
            if (item.serializedObject == property.serializedObject)
            {
                item.Repaint();
                return;
            }
        }
    }


    #region SerializedProperty Get Parent

    // Found here http://answers.unity.com/answers/425602/view.html
    // Update here https://gist.github.com/AdrienVR/1548a145c039d2fddf030ebc22f915de to support inherited private members.
    /// <summary>
    /// Get parent object of SerializedProperty
    /// </summary>
    public static object GetParent(this SerializedProperty prop)
    {
        var path = prop.propertyPath.Replace(".Array.data[", "[");
        object obj = prop.serializedObject.targetObject;
        var elements = path.Split('.');
        foreach (var element in elements.Take(elements.Length - 1))
        {
            if (element.Contains("["))
            {
                var elementName = element.Substring(0, element.IndexOf("[", StringComparison.Ordinal));
                var index = Convert.ToInt32(element.Substring(element.IndexOf("[", StringComparison.Ordinal))
                    .Replace("[", "").Replace("]", ""));
                obj = GetValueAt(obj, elementName, index);
            }
            else
            {
                obj = GetFieldValue(obj, element);
            }
        }

        return obj;


        object GetValueAt(object source, string name, int index)
        {
            var enumerable = GetFieldValue(source, name) as IEnumerable;
            if (enumerable == null) return null;

            var enm = enumerable.GetEnumerator();
            while (index-- >= 0)
                enm.MoveNext();
            return enm.Current;
        }

        object GetFieldValue(object source, string name)
        {
            if (source == null)
                return null;

            foreach (var type in GetHierarchyTypes(source.GetType()))
            {
                var f = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
                if (f != null)
                    return f.GetValue(source);

                var p = type.GetProperty(name,
                    BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (p != null)
                    return p.GetValue(source, null);
            }

            return null;


            IEnumerable<Type> GetHierarchyTypes(Type sourceType)
            {
                yield return sourceType;
                while (sourceType.BaseType != null)
                {
                    yield return sourceType.BaseType;
                    sourceType = sourceType.BaseType;
                }
            }
        }
    }

    #endregion

    #endregion

    #region AssetDataBase

    public static string AssetGuid(this string assetPath)
    {
        return AssetDatabase.AssetPathToGUID(assetPath);
    }

    public static string AssetPath(this string assetPath)
    {
        return AssetDatabase.GUIDToAssetPath(assetPath);
    }

    #endregion

#endif
}